using System;
using System.Collections.Generic;
using System.Xml;
using System.Net;
using System.IO;
using System.Globalization;

namespace TwitterLib
{
    /// <summary>
    /// .NET wrapper for Twitter API calls
    /// </summary>
    public class TwitterNet
    {
        #region Private Fields

        private string username;
        private string password;
        private string publicTimelineUrl;
        private string friendsTimelineUrl;
        private string userTimelineUrl;
        private string updateUrl;
        private string friendsUrl;
        private string followersUrl;
        private string format;
	
        // TODO: might need to fix this for globalization
        private string twitterCreatedAtDateFormat = "ddd MMM dd HH:mm:ss zzzz yyyy"; // Thu Apr 26 01:36:08 +0000 2007
        private string twitterSinceDateFormat = "ddd MMM dd yyyy HH:mm:ss zzzz";

        private static int characterLimit;

        #endregion

        #region Public Properties

        /// <summary>
        /// Twitter username
        /// </summary>
        public string UserName
        {
            get { return username; }
            set { username = value; }
        }

        /// <summary>
        /// Twitter password
        /// </summary>
        public string Password
        {
            get { return password; }
            set { password = value; }
        }

        /// <summary>
        ///  Url to the Twitter Public Timeline. Defaults to http://twitter.com/statuses/public_timeline
        /// </summary>
        /// <remarks>
        /// This value should only be changed if Twitter API urls have been changed on http://twitter.com/help/api
        /// </remarks>
        public string PublicTimelineUrl
        {
            get
            {
                if (string.IsNullOrEmpty(publicTimelineUrl))
                    return "http://twitter.com/statuses/public_timeline";
                else
                    return publicTimelineUrl;
            }
            set { publicTimelineUrl = value; }
        }

        /// <summary>
        /// Url to the Twitter Friends Timeline. Defaults to http://twitter.com/statuses/friends_timeline
        /// </summary>
        /// <remarks>
        /// This value should only be changed if Twitter API urls have been changed on http://twitter.com/help/api
        /// </remarks>
        public string FriendsTimelineUrl
        {
            get
            {
                if (string.IsNullOrEmpty(friendsTimelineUrl))
                    return "http://twitter.com/statuses/friends_timeline";
                else
                    return friendsTimelineUrl;
            }
            set { friendsTimelineUrl = value; }
        }

        /// <summary>
        /// Url to the user's timeline. Defaults to http://twitter.com/statuses/user_timeline
        /// </summary>
        /// <remarks>
        /// This value should only be changed if Twitter API urls have been changed on http://twitter.com/help/api
        /// </remarks>
        public string UserTimelineUrl
        {
            get
            {
                if (string.IsNullOrEmpty(friendsTimelineUrl))
                    return "http://twitter.com/statuses/user_timeline";
                else
                    return userTimelineUrl;
            }
            set { userTimelineUrl = value; }
        }

        /// <summary>
        /// Url to the Twitter HTTP Post. Defaults to http://twitter.com/statuses/update
        /// </summary>
        /// <remarks>
        /// This value should only be changed if Twitter API urls have been changed on http://twitter.com/help/api
        /// </remarks>
        public string UpdateUrl
        {
            get
            {
                if (string.IsNullOrEmpty(updateUrl))
                    return "http://twitter.com/statuses/update";
                else
                    return updateUrl;
            }
            set { updateUrl = value; }
        }

        /// <summary>
        /// Url to the user's friends. Defaults to http://twitter.com/statuses/friends
        /// </summary>
        /// <remarks>
        /// This value should only be changed if Twitter API urls have been changed on http://twitter.com/help/api
        /// </remarks>
        public string FriendsUrl
        {
            get
            {
                if (string.IsNullOrEmpty(friendsUrl))
                    return "http://twitter.com/statuses/friends";
                else
                    return friendsUrl;
            }
            set { friendsUrl = value; }
        }

        /// <summary>
        /// Url to the user's followers. Defaults to http://twitter.com/statuses/followers
        /// </summary>
        /// <remarks>
        /// This value should only be changed if Twitter API urls have been changed on http://twitter.com/help/api
        /// </remarks>
        public string FollowersUrl
        {
            get
            {
                if (string.IsNullOrEmpty(followersUrl))
                    return "http://twitter.com/statuses/followers";
                else
                    return followersUrl;
            }
            set { followersUrl = value; }
        }


        /// <summary>
        /// The format of the results from the twitter API. Ex: .xml, .json, .rss, .atom. Defaults to ".xml"
        /// </summary>
        /// <remarks>
        /// This value should only be changed if Twitter API urls have been changed on http://twitter.com/help/api
        /// </remarks>
        public string Format
        {
            get
            {
                if (string.IsNullOrEmpty(format))
                    return ".xml";
                else
                    return format;
            }
            set { format = value; }
        }

        /// <summary>
        /// The number of characters available for the Tweet text. Defaults to 140.
        /// </summary>
        /// <remarks>
        /// This value should only be changed if the character limit on Twitter.com has been changed.
        /// </remarks>
        public static int CharacterLimit
        {
            get
            {
                if (characterLimit == 0)
                    return 140;
                else
                    return characterLimit;
            }
            set { characterLimit = value; }
        }

        #endregion

        #region Constructors

        /// <summary>
        /// Unauthenticated constructor
        /// </summary>
        public TwitterNet()
        {
        }

        /// <summary>
        /// Authenticated constructor
        /// </summary>
        public TwitterNet(string username, string password)
        {
            this.username = username;
            this.password = password;
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Retrieves the public timeline
        /// </summary>
        public Tweets GetPublicTimeline()
        {
            return RetrieveTimeline(Timeline.Public);
        }

        /// <summary>
        /// Retrieves the public timeline. Narrows the result to after the since date.
        /// </summary>
        public Tweets GetPublicTimeline(string since)
        {
            return RetrieveTimeline(Timeline.Public, since);
        }

        /// <summary>
        /// Retrieves the friends timeline
        /// </summary>
        public Tweets GetFriendsTimeline()
        {
            if (!string.IsNullOrEmpty(username))
                return RetrieveTimeline(Timeline.Friends);
            else
                return RetrieveTimeline(Timeline.Public);
        }

        /// <summary>
        /// Retrieves the friends timeline. Narrows the result to after the since date.
        /// </summary>
        public Tweets GetFriendsTimeline(string since)
        {
            if (!string.IsNullOrEmpty(username))
                return RetrieveTimeline(Timeline.Friends, since);
            else
                return RetrieveTimeline(Timeline.Public, since);
        }

        /// <summary>
        /// Retrieves the friends timeline. Narrows the result to after the since date.
        /// </summary>
        public Tweets GetFriendsTimeline(string since, string userId)
        {
                return RetrieveTimeline(Timeline.Friends, string.Empty, userId);
        }


        public Tweets GetUserTimeline(string userId)
        {
            return RetrieveTimeline(Timeline.User, "", userId);
        }


        /// <summary>
        /// Post new tweet to Twitter
        /// </summary>
        public void AddTweet(string text)
        {
            // Create the web request  
            HttpWebRequest request = WebRequest.Create(UpdateUrl) as HttpWebRequest;

            // Add authentication to request  
            request.Credentials = new NetworkCredential(username, password);

            request.Method = "POST";

            // Set values for the request back
            request.ContentType = "application/x-www-form-urlencoded";
            string param = "status=" + text;
            request.ContentLength = param.Length;

            // Write the request paramater
            StreamWriter stOut = new StreamWriter(request.GetRequestStream(), System.Text.Encoding.ASCII);
            stOut.Write(param);
            stOut.Close();

            // Do the request to get the response
            using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
            {
                // Get the response stream  
                StreamReader reader = new StreamReader(response.GetResponseStream());
            }
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Retrieves the specified timeline from Twitter
        /// </summary>
        /// <returns>Collection of Tweets</returns>
        private Tweets RetrieveTimeline(Timeline timeline)
        {
            return RetrieveTimeline(timeline, string.Empty);
        }

        /// <summary>
        /// Retrieves the specified timeline from Twitter
        /// </summary>
        /// <returns>Collection of Tweets</returns>
        private Tweets RetrieveTimeline(Timeline timeline, string since)
        {
            return RetrieveTimeline(timeline, since, string.Empty);
        }

        /// <summary>
        /// The Main function for interfacing with the Twitter API
        /// </summary>
        /// <param name="timeline"></param>
        /// <param name="since"></param>
        /// <param name="userId"></param>
        /// <returns>Collection of Tweets. Twitter limits the max to 20.</returns>
        private Tweets RetrieveTimeline(Timeline timeline, string since, string userId)
        {
            Tweets tweets = new Tweets();

            string timelineUrl = string.Empty;

            switch (timeline)
            {
                case Timeline.Public:
                    timelineUrl = PublicTimelineUrl;
                    break;
                case Timeline.Friends:
                    timelineUrl = FriendsTimelineUrl;
                    break;
                case Timeline.User:
                    timelineUrl = UserTimelineUrl;
                    break;
                default:
                    timelineUrl = PublicTimelineUrl;
                    break;
            }

            if (!string.IsNullOrEmpty(userId))
                timelineUrl += "/" + userId + Format;
            else
                timelineUrl += Format;

            if (!string.IsNullOrEmpty(since))
            {
                DateTime sinceDate = new DateTime();
                DateTime.TryParse(since, out sinceDate);

                // Go back a minute to compensate for latency.
                sinceDate = sinceDate.AddMinutes(-1);

                string sinceDateString = sinceDate.ToString(twitterSinceDateFormat);

                timelineUrl = timelineUrl + "?since=" + sinceDateString;
            }
            else
            {
                // Twitter caches the timeline and it's not getting the latest tweets, hack to bypass twitter cache.
                // TODO: Consider removing if Twitter fixes this issue.
                timelineUrl = timelineUrl + "?since=" + DateTime.Now;
            }

            // Create the web request
            HttpWebRequest request = WebRequest.Create(timelineUrl) as HttpWebRequest;

            // Friends timeline request need to be authenticated
            if (timeline == Timeline.Friends)
            {
                // Add credendtials to request  
                request.Credentials = new NetworkCredential(username, password);
            }

            try
            {
                // Get the Response  
                using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
                {
                    // Get the response stream  
                    StreamReader reader = new StreamReader(response.GetResponseStream());

                    // Create a new XmlDocument  
                    XmlDocument doc = new XmlDocument();

                    // Load data  
                    doc.Load(reader);

                    // Get statues  with XPath  
                    XmlNodeList nodes = doc.SelectNodes("/statuses/status");

                    foreach (XmlNode node in nodes)
                    {
                        Tweet tweet = new Tweet();
                        tweet.Id = double.Parse(node.SelectSingleNode("id").InnerText);
                        tweet.Text = node.SelectSingleNode("text").InnerText;

                        string dateString = node.SelectSingleNode("created_at").InnerText;
                        if (!string.IsNullOrEmpty(dateString))
                        {
                            tweet.DateCreated = DateTime.ParseExact(
                                dateString,
                                twitterCreatedAtDateFormat,
                                CultureInfo.CurrentCulture, DateTimeStyles.AllowWhiteSpaces);
                        }

                        User user = new User();
                        XmlNode userNode = node.SelectSingleNode("user");
                        user.Name = userNode.SelectSingleNode("name").InnerText;
                        user.ScreenName = userNode.SelectSingleNode("screen_name").InnerText;
                        user.ImageUrl = userNode.SelectSingleNode("profile_image_url").InnerText;
                        user.SiteUrl = userNode.SelectSingleNode("url").InnerText;
                        user.Location = userNode.SelectSingleNode("location").InnerText;
                        tweet.User = user;

                        tweets.Add(tweet);
                    }

                    tweets.Save();
                }
            }
            catch (WebException webExcp)
            {
                // Get the WebException status code.
                WebExceptionStatus status = webExcp.Status;
                // If status is WebExceptionStatus.ProtocolError, 
                //   there has been a protocol error and a WebResponse 
                //   should exist. Display the protocol error.
                if (status == WebExceptionStatus.ProtocolError)
                {
                    // Get HttpWebResponse so that you can check the HTTP status code.
                    HttpWebResponse httpResponse = (HttpWebResponse)webExcp.Response;

                    // 304 Not modified = no new tweets so ignore error.  Rethrow every other WebException.
                    if ((int)httpResponse.StatusCode != 304)
                        throw webExcp;
                }
            }
            return tweets;
        }

        #endregion
    }

    /// <summary>
    /// Enumeration of available Twitter timelines
    /// </summary>
    public enum Timeline
    {
        Public,
        Friends,
        User
    }
}